/*
 * Package il.ac.biu.cs.grossmm.impl.activeData
 * File KeyImpl.java
 * Created on Jul 20, 2006
 *
 */
package il.ac.biu.cs.grossmm.api.keys;

/**
 * An implementation of <tt>Key</tt> interface based on a
 * resizable array.
 */
public final class ArrayKey extends KeyBase implements Key {
    private KeyEntry[] entries;
    private final Object value;
    private int size;
    private int hashCode;
    
    public ArrayKey() {
        this(null);
    }
    
    public ArrayKey(Object value) {
        this.value = value;
        entries = new KeyEntry[2];
        
        recalcHashCode();
    }
    
    public ArrayKey(Object value, KeyEntry[] entries) {
        this.value = value;
        this.entries = entries;
        this.size = entries == null ? 0 : entries.length;
        
        recalcHashCode();
    }
    
    /**
     * Creates a new key based on existing key with a set of key-entries 
     * added. This operation does not change key provided as an argument.
     * @param key the base key
     * @param entries key entries to add
     */
    public ArrayKey(Key key, KeyEntry[] entries) {
    	this.value = key.value();
    	this.entries = new KeyEntry[key.size()+entries.length];
    	
    	int len = key.size();
    	int sz=0;
    	out: for( int i=0; i<len; i++ ) {
    		KeyEntry ke = key.entry(i);
    		for( KeyEntry ke2: entries ){
    			if(ke2.getAttribute().equals(ke.getAttribute()))
    				continue out;
    		}
    		
    		this.entries[sz++] = ke;
    	}
    	
    	System.arraycopy(entries, 0, this.entries, sz, entries.length);
    	
    	this.size = sz + entries.length;
    	
    	recalcHashCode();
    }
    
    /**
     * Creates a new key with all entries in a given existing key 
     * and the given entry (replaces the entry with the same attribute
     * in the existing key, if such entry exists). 
     * This operation does not change key provided as an argument.
     * @param key the base key
     * @param entriey key entry to add
     */
    public ArrayKey(Key key, KeyEntry entry) {
    	this.value = key.value();
    	this.entries = new KeyEntry[key.size()+1];
    	
    	int len = key.size();
    	int sz =0;

    	boolean seen = false;
    	
    	for( int i=0; i<len; i++ ) {
    		KeyEntry ke = key.entry(i);
			if(! seen && entry.getAttribute().equals(ke.getAttribute())) {
				seen = true;
				this.entries[sz++] = entry;
			 } else
				 this.entries[sz++] = ke;
    	}

    	if(! seen ) {
    		this.entries[sz++] = entry;
    	}
    	
    	this.size = sz;
    	
    	recalcHashCode();
    }

    private void recalcHashCode() {
        if( value == null )
            hashCode = 0;
        else
            hashCode = value.hashCode();
        
        for( int i=0 ; i<size; i++ ) {
            KeyEntry ke = entries[i];
            hashCode += ke.getAttribute().hashCode();
            hashCode += ke.getKey().hashCode();
        }
    }
    
    public void addKey(KeyEntry e)
	{
    	if( entries == null ) 
             entries = new KeyEntry[4];
         else if( entries.length == size ) {
             KeyEntry[] newEntries = new KeyEntry[size*2];
             System.arraycopy(entries, 0, newEntries, 0, size);
             entries = newEntries;
         }
         
         entries[size++] = e;
         
         recalcHashCode();
	}
    
    public void addKey(Attribute attribute, Key key) {
        addKey(new KeyEntry(attribute, key));
    }
    
    public void addValue(Attribute attribute, Object value) {
        addKey(attribute, new SimpleKey(value));
    }

    public KeyEntry entry(int i) {
        return entries[i];
    }

    public Key subkey(Attribute a) {
        for( KeyEntry ke : entries )
            if( ke.getAttribute().equals(a) )
                return ke.getKey();
        
        return null;
    }

    public Object value() {
        return value;
    }

    public int size() {
        return size;
    }

    @Override
    public boolean equals(Object obj) {
        if( ! (obj instanceof Key) )
            return false;
        
        Key k = (Key) obj;
        
        if( k == this )
            return true;
        
        if( value == null ) {
            if( k.value() != null)
                return false;
        } else if( ! value.equals(k.value()) )
            return false;
        
        if( size != k.size() )
            return false;
        
        for (int i = 0; i < size; i++) {
            KeyEntry ke = entries[i];
            Key key = k.subkey(ke.getAttribute());
            if( key == null || ! ke.getKey().equals(key) )
                return false;
        }
        
        return true;
    }
    
    @Override
    public int hashCode() {
        return hashCode;
    }
    
    @Override
    public String toString() {
    	StringBuffer sb = new StringBuffer();
        if( size >0 )
        	sb.append("\n");
        
        sb.append(value);
        
        if( entries.length > 0 ) {
            
            sb.append(" {\n");
            
            int c = size; 
            for( KeyEntry e : entries ) {
               if( c-- == 0 )
                   break;
               
               sb.append('\t');
               sb.append(e.getAttribute());
               sb.append(" = ");
               Key key = e.getKey();
               if( key instanceof ArrayKey )
                   sb.append(((ArrayKey) key).toString(1));
               else
                   sb.append(key.toString());
                       
               sb.append('\n');
            }
            
            sb.append("}");
        }
        
        return sb.toString();
    }
    
    public String toString(int tabCount) {
        String tabs= "\t\t\t\t\t\t\t\t\t\t\t\t";
        if( tabCount < tabs.length() )
            tabs = tabs.substring(0, tabCount);
                        
        StringBuffer sb = new StringBuffer(value.toString());
        
        if( entries.length > 0 ) {
            
            sb.append(" {\n");
            
            for( KeyEntry e : entries ) {
               sb.append(tabs);
               sb.append('\t');
               sb.append(e.getAttribute());
               sb.append(" = ");
               Key key = e.getKey();
               if( key instanceof ArrayKey )
                   sb.append(((ArrayKey) key).toString(tabCount+1));
               else
                   sb.append(key.toString());
               sb.append('\n');
            }
            
            sb.append("}");
        }
        
        return sb.toString();
    }

	
}
